package com.laamella.syntherface.synths.alesis;

import com.laamella.syntherface.synths.midi.BinaryField;
import com.laamella.syntherface.synths.midi.Bits;
import com.laamella.syntherface.synths.midi.BinaryField.BinaryFieldWithOffsetAndParameterModel;
import com.laamella.syntherface.synths.models.ParameterModel;

public final class AlesisUtilities {

	private AlesisUtilities() {
	}

	/**
	 * Map a signed byte in Alesis's decoded 8 bit sysex format.
	 */
	public static class SignedInt8Field extends BinaryFieldWithOffsetAndParameterModel {

		public SignedInt8Field(int offset, ParameterModel parameterModel) {
			super(offset, parameterModel);
		}

		@Override protected void decode(byte[] data) throws Exception {
			setValue(data[offset]);
		}

		@Override protected void encode(byte[] data) throws Exception {
			data[offset] = (byte) getValue();
		}

	}

	/**
	 * Map an unsigned byte in Alesis's decoded 8 bit sysex format.
	 */
	public static class UnsignedInt8Field extends BinaryFieldWithOffsetAndParameterModel {

		public UnsignedInt8Field(int offset, ParameterModel parameterModel) {
			super(offset, parameterModel);
		}

		@Override protected void decode(byte[] data) throws Exception {
			// TODO
			setValue(data[offset]);
		}

		@Override protected void encode(byte[] data) throws Exception {
			// TODO
			data[offset] = (byte) getValue();
		}

	}

	/**
	 * Map a signed 16 bit int in Alesis's decoded 8 bit sysex format.
	 */
	public static class SignedInt16Field extends BinaryFieldWithOffsetAndParameterModel {

		public SignedInt16Field(int offset, ParameterModel parameterModel) {
			super(offset, parameterModel);
		}

		@Override protected void decode(byte[] data) throws Exception {
			int value = data[offset] << 8 & data[offset + 1];
			setValue(value);
		}

		@Override protected void encode(byte[] data) throws Exception {
			int value = getValue();
			data[offset] = (byte) ((value >> 8) & 0xFF);
			data[offset + 1] = (byte) (value & 0xFF);
		}

	}

	/**
	 * The Alesis checksum field.  
	 * <p>Thank you, Bernard Escaillas, for figuring this out.
	 * 
	 * @param decoded
	 */
	public static class ChecksumField extends BinaryField {
		private static final int ChecksumOffset = 15;
		private final int startOffset;
		private final int endOffset;
		
		public static long calculateChecksum(byte[] data, int startOffset, int endOffset) {
			long calculatedChecksum = 0;

			for (int i = startOffset; i < endOffset; i += 4) {
				calculatedChecksum += Bits.getUnsignedInt32At(data, i);
				calculatedChecksum &= 0xFFFFFFFFl;
			}
			calculatedChecksum &= 0xFFFFFFFFl;
			calculatedChecksum =0x100000000l - calculatedChecksum;
			return calculatedChecksum;
		}

		public ChecksumField(int startOffset, int endOffset){
			this.startOffset=startOffset;
			this.endOffset=endOffset;
		}

		@Override protected void decode(byte[] data) throws Exception {
			long calculatedChecksum = calculateChecksum(data, startOffset, endOffset);
			long foundChecksum = Bits.getUnsignedInt32At(data, 15);

			if (foundChecksum != calculatedChecksum) {
				throw new Exception("Checksum bad!");
			}
			System.out.println("Checksum OK.");
		}

		@Override protected void encode(byte[] data) throws Exception {
			Bits.setUnsignedInt32At(data, ChecksumOffset, calculateChecksum(data, startOffset, endOffset));
		}
		
	}
	/**
	 * Convert Alesis's stupid sysex metaformat.
	 * 
	 * @param data
	 * @return
	 */
	public static byte[] convertFromSysExToAlesisByteArray(byte[] data) {
		// TODO implement;
		return data;
	}

	/**
	 * Map an unsigned 32 bit int in Alesis's decoded 8 bit sysex format.
	 */
	public static class UnsignedInt32Field extends BinaryFieldWithOffsetAndParameterModel {

		public UnsignedInt32Field(int offset, ParameterModel parameterModel) {
			super(offset, parameterModel);
		}

		@Override protected void decode(byte[] data) throws Exception {
			setValue((int) Bits.getUnsignedInt32At(data, offset));
		}

		@Override protected void encode(byte[] data) throws Exception {
			Bits.setUnsignedInt32At(data, offset, getValue());
		}
	}

	/**
	 * Convert Alesis's stupid sysex metaformat.
	 * 
	 * @param sysex
	 * @return
	 * @throws Exception
	 */
	public static byte[] convertFromAlesisSysExToByteArray(byte[] sysex) throws Exception {
		final int StartOffset=1;
		int blocks = (sysex.length-2) / 8;
		int newSize = blocks * 7;
		// System.out.println("sysex size:" + sysex.length + " bytes
		// length:" + newSize);
		byte[] bytes = new byte[newSize];
		for (int blockNumber = 0; blockNumber < blocks; blockNumber++) {
			int sysexBlockOffset = blockNumber * 8+StartOffset;
			int highBits = sysex[sysexBlockOffset];
			// System.out.println(highBits);
			// String hexes = "";
			// String ascii = "";
			for (int byteInBlock = 0; byteInBlock < 7; byteInBlock++) {
				int currentByte = sysex[sysexBlockOffset + 1 + byteInBlock];
				if ((highBits & 0x40) > 0) {
					currentByte = currentByte | 0x80;
				}
				highBits <<= 1;
				int bytesBlockOffset = blockNumber * 7 + byteInBlock;
				bytes[bytesBlockOffset] = (byte) currentByte;

				// hexes += byteToHex(currentByte) + " ";

				// ascii += (char) currentByte;
			}
			// System.out.println(hexes + ascii);
		}

		return bytes;
	}
}
